##############################################################################
#
# Node.pm
# =======
#
# Author: Steven Liang <stliang@gmail.com>
#
# Purpose:
#   Parent class for all types of network nodes
#   You can extend this class into different types
#   of network nodes.  So that different nodes would
#   contains its own behavior / method
#
# Assumptions:
#   1.) A network node has an IP address
#   2.) User can login to a network node
#   3.) Login session can be done via telnet
#   4.) The Expect package is installed
#
# Supported methods:
#   new($IP, $userName, $passWord; $port || 23);
#   DESTROY()
#   getExpPtr();
#   getIP();
#   getUserName();
#   getPassWord();
#   getPort();
#   cmdPrompt($promptPattern)
#   expectTimeout (; $timeout)
#   openTelnet()
#   closeTelnet(0 || 1)
#   login($userNamePrompt, $passWordPrompt, $cmdPrompt; $timeout || 60);
#   send($sendString; $expectedString || $_cmdPrompt)
#   getOutPut()
#   log($fileName)
#   stopLog()
#   appendLog($fileName)
#   logMessage($message)
#
# Note:
#   Variable after ";" is optional variable and value after "||" is 
#   default value
#
##############################################################################

package Node;
$VERSION = 1.01;

use strict;
use Expect;
use AutoTest::Exceptions;

use vars qw($exp $_cmdPrompt $_expectTimeout $bbLogging);

# Constructor: new
# Purpose:     Instantiate a Node object
# Parameters:
#       1.) ip : IP address of the node
#       2.) userName : User name used for login
#       3.) passWord : Password used for login
#       4.) port : A port to use for telnet, default value is 23
#
# Returns:    An instance of Network::Node class
#
# Note:
# Optional parameter can be handle like this too:
# my($myParam) = (@_ > 0) ? shift @_ : 1;  ## 1 = default value if no param
sub new ($$$;$) {
	my $class = shift;
	my $self = { };
	bless $self;
	$self->{"ip"} = shift;
	$self->{"userName"} = shift;
	$self->{"passWord"} = shift;
	$self->{"port"} = defined($_[0]) ? shift : 23; # Default telnet port
	$self->{bLogging} = 0;
	$self->expectTimeout(60); # Default expect timeout value is 60 seconds
	return $self;
}

# Destructor: DESTROY
# Purpose:    Release resource
# Parameters: None
# Returns:    Void
sub DESTROY () {
	my $self = shift;
	$self->closeTelnet();
	if (defined($self->{exp})) {undef $self->{exp}}
}

# Method:	getExpPtr
# Purpose:	Allow user to dereference this Expect object
# Parameters:	None
# Returns:	Address-of this Expect object
#
# Note:		Remember to use the dereference operator "$" so you will 
#		end up with two dollar sign for your scaler.  
#		Also check return null or undefind
sub getExpPtr () {
    my $self = shift;
    return \$self->{exp};
}

# Method:     getIP
# Purpose:    Read only IP address of instance Node
# Parameters: None
# Returns:    IP address of instance Node
sub getIP () {
    my $self = shift;
    return $self->{ip};
}

# Method:     getUserName
# Purpose:    Read only userName of instance Node
# Parameters: None
# Returns:    userName of instance Node
sub getUserName () {
    my $self = shift;
    return $self->{userName};
}

# Method:     getPassWord
# Purpose:    Read only passWord of instance Node
# Parameters: None
# Returns:    passWord of instance Node
sub getPassWord () {
    my $self = shift;
    return $self->{passWord};
}

# Method:     getPort
# Purpose:    Read only port of instance Node
# Parameters: None
# Returns:    Port of instance Node
sub getPort () {
    my $self = shift;
    return $self->{port};
}

# Method:     cmdPrompt
# Purpose:    get or set the global _cmdPrompt variable
#             This is useful because in the process of
#             interacting with a node, the user prompt
#             could change.  Use this method to match
#             user prompt would save you the effort of
#             specifing what prompt to expect each time
#             a send command is executed
#
# Parameters: optional string
# Returns:    The value of _cmdPrompt
sub cmdPrompt ($) {
    my $self = shift;
    $self->{_cmdPrompt} = $_[0] if defined($_[0]);
    return $self->{_cmdPrompt};
}

# Method:     expectTimeout
# Purpose:    Get or set this objects expect timeout value
#	      expectTimeout affects the "send" sub.
# Parameters: optional digit value
# Returns:    The value of _expectTimeout
sub expectTimeout ($) {
    my $self = shift;
    $self->{_expectTimeout} = $_[0] if defined($_[0]);
    return $self->{_expectTimeout};
}


# Method:     openTelnet
# Purpose:    Open a telnet session to instance node
# Parameters: None
# Returns:    Void
sub openTelnet () {
	my ($self, @args) = @_;
	$self->{exp} = new Expect;
	$self->{exp}->raw_pty(1);  
	$self->{exp}->spawn("telnet $self->{ip} $self->{port}")
	or AutoTest::Exception::Connection->throw("Unable to spawn telnet");
	#or die "Cannot spawn telnet: $!\n";
}

# Method:    closeTelnet
# Purpose:   Close a telnet session to instance node
# Parameters: 
#	1.) isSoft: boolean value  
#		    isSoft = 0 will call hard_close
#		    isSoft = 1 will call soft_close
#		    default value is 0
#
# Returns:   Void
sub closeTelnet () {
	my ($self, $isSoft) = @_;
	if (!defined($isSoft)) {$isSoft = 0} 
	if (defined($self->{exp})) {
		if ($isSoft) {
			$self->{exp}->soft_close();
		} else {
			$self->{exp}->hard_close();
		}
	}
}

# Method:     login
# Purpose:    Automatic login of instance node
# Parameters: 
#	      1.) userNamePrompt: The user name prompt
#	      2.) passWordPrompt: The password prompt
#	      3.) cmdPrompt: Command prompt after successful login
#	      4.) timeout: Optional timeout value
#
# Returns:    Void
sub login ($$$;$) {
	my ($self) = shift;
	my ($userNamePrompt) = shift;
	my ($passWordPrompt) = shift;
	my ($cmdPrompt) = shift;
	my ($timeout) = defined($_[0]) ? shift : 60;
	my $spawn_ok;
	$self->cmdPrompt($cmdPrompt);
	$self->{exp}->expect($timeout,
             	[
              	$userNamePrompt,
              	sub {
                	$spawn_ok = 1;
                	$self->{exp}->send("$self->{userName}\n");
                	exp_continue;
              	    }
             	],
             	[
              	$passWordPrompt,
              	sub {
                	$self->{exp}->send("$self->{passWord}\n");
                	exp_continue;
              	    }
             	],
             	[
              	eof =>
              	sub {
                	if ($spawn_ok) {
			AutoTest::Exception::Expect->throw("premature EOF in login.");
                  	#die "ERROR: premature EOF in login.\n";
                	} else {
			AutoTest::Exception::Connection->throw("could not spawn telnet.");
                  	#die "ERROR: could not spawn telnet.\n";
                	}
              	    }
             	],
             	[
              	timeout =>
              	sub {
			AutoTest::Exception::Expect->throw("Time out expecting login prompt.");
                	#die "No login.\n";
              	}
             	],
             	'-re', $cmdPrompt, #' wait for shell prompt, then exit expect
        );
}

# Method:     send
# Purpose:    Short hand for send expect
# Parameters: 
#	      1.) sendString: string to send
#	      2.) expectedString: a pattern to match
#                 has a default value of _cmdPrompt
#
# Returns:    Void
sub send ($;$) {
	my ($self) = shift;
	my ($sendString) = shift;
	my ($expectedString) = defined($_[0]) ? shift : $self->{_cmdPrompt};
	$self->{exp}->send("$sendString\n");
	$self->{exp}->expect($self->expectTimeout(),
		[
                 $expectedString,
        	        sub {
               		    }
                ],
		[
		 timeout => 
			sub
			{
			 AutoTest::Exception::Expect->throw("Timeout expecting $expectedString");
			}
		 ],
	);
}

# Method:     getOutPut
# Purpose:    Allow user to copy the Expect buffer
# Parameters: None
# Returns:    Accumulated output before the expect() call finished
sub getOutPut () {
	my ($self) = shift;
	return $self->{exp}->before();
}


# Method:     log
# Purpose:    Log Expect session
# Parameters: fileName
# Returns:    file handle of log file
#
# Note:       User this method after openTelnet
sub log ($) {
	my ($self) = shift;
	my ($fileName) = shift;
	$self->{bLogging} = 1;
	if (!defined($self->{"exp"})) {
		AutoTest::Exception::Expect->throw("No Expect instance, call openTelnet to get one");
	}
	$self->{"exp"}->log_file(undef);
	return $self->{"exp"}->log_file($fileName, "w");
}

# Method:     stopLog
# Purpose:    Stop logging expect session to file
# Parameters: None
# Returns:    Void
sub stopLog () {
	my ($self) = shift;
	$self->{bLogging} = 0;
	$self->{"exp"}->log_file(undef);
}

# Method:     appendLog
# Purpose:    Log expect session but don't override
# Parameters: fileName
# Returns:    file handle of log file
#
# Note:       User this method after openTelnet
sub appendLog ($) {
	my ($self) = shift;
	my ($fileName) = shift;
	$self->{bLogging} = 1;
	return $self->{"exp"}->log_file($fileName);
}

# Method:       logMessage
# Purpose:      Insert user specified message to log file
# Parameters:   message
# Returns:      Void
# Assumption:   sub log or appendLog is running
sub logMessage ($) {
	my ($self) = shift;
	my ($message) = shift;
	if (!defined($self->{"exp"})) {
		AutoTest::Exception::Expect->throw("No Expect instance, call openTelnet to get one");
	}
	$self->{"exp"}->print_log_file($message) if $self->{bLogging};
}
